import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" import { createEffect, For, Show } from "solid-js" import { withActor } from "~/context/auth.withActor" import { createStore } from "solid-js/store" import styles from "./member-section.module.css" import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js" import { Actor } from "@opencode-ai/console-core/actor.js" import { User } from "@opencode-ai/console-core/user.js" import { RoleDropdown } from "./role-dropdown" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError, localizeError } from "~/lib/form-error" const listMembers = query(async (workspaceID: string) => { "use server" return withActor(async () => { return { members: await User.list(), actorID: Actor.userID(), actorRole: Actor.userRole(), } }, workspaceID) }, "member.list") const inviteMember = action(async (form: FormData) => { "use server" const email = (form.get("email") as string | null)?.trim() if (!email) return { error: formError.emailRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } const role = form.get("role") as (typeof UserRole)[number] | null if (!role) return { error: formError.roleRequired } const limit = form.get("limit") as string | null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } return json( await withActor( () => User.invite({ email, role, monthlyLimit }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.create") const removeMember = action(async (form: FormData) => { "use server" const id = form.get("id") as string | null if (!id) return { error: formError.idRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } return json( await withActor( () => User.remove(id) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.remove") const updateMember = action(async (form: FormData) => { "use server" const id = form.get("id") as string | null if (!id) return { error: formError.idRequired } const workspaceID = form.get("workspaceID") as string | null if (!workspaceID) return { error: formError.workspaceRequired } const role = form.get("role") as (typeof UserRole)[number] | null if (!role) return { error: formError.roleRequired } const limit = form.get("limit") as string | null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } return json( await withActor( () => User.update({ id, role, monthlyLimit }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.update") function MemberRow(props: { member: any workspaceID: string actorID: string actorRole: string roleOptions: { value: string; label: string; description: string }[] }) { const i18n = useI18n() const submission = useSubmission(updateMember) const isCurrentUser = () => props.actorID === props.member.id const isAdmin = () => props.actorRole === "admin" const [store, setStore] = createStore({ editing: false, selectedRole: props.member.role as (typeof UserRole)[number], limit: "", }) createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { setStore("editing", false) } }) function show() { while (true) { submission.clear() if (!submission.result) break } setStore("editing", true) setStore("selectedRole", props.member.role) setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "") } function hide() { setStore("editing", false) } function getUsageDisplay() { const currentUsage = (() => { const dateLastUsed = props.member.timeMonthlyUsageUpdated if (!dateLastUsed) return 0 const current = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", timeZone: "UTC", }) const lastUsed = dateLastUsed.toLocaleDateString(undefined, { year: "numeric", month: "long", timeZone: "UTC", }) return current === lastUsed ? (props.member.monthlyUsage ?? 0) : 0 })() const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : i18n.t("workspace.members.noLimitLowercase") return `$${(currentUsage / 100000000).toFixed(2)} / ${limit}` } const roleLabel = (value: string) => props.roleOptions.find((option) => option.value === value)?.label ?? value return ( {props.member.authEmail ?? props.member.email} {roleLabel(props.member.role)}}> setStore("selectedRole", value as (typeof UserRole)[number])} /> {getUsageDisplay()}}> setStore("limit", e.currentTarget.value)} placeholder={i18n.t("workspace.members.noLimit")} min="0" /> {props.member.timeSeen ? "" : i18n.t("workspace.members.invited")}
} >
) } export function MemberSection() { const params = useParams() const i18n = useI18n() const language = useLanguage() const data = createAsync(() => listMembers(params.id!)) const submission = useSubmission(inviteMember) const [store, setStore] = createStore({ show: false, selectedRole: "member" as (typeof UserRole)[number], limit: "", }) let input: HTMLInputElement const roleOptions = [ { value: "admin", label: i18n.t("workspace.members.role.admin"), description: i18n.t("workspace.members.role.adminDescription"), }, { value: "member", label: i18n.t("workspace.members.role.member"), description: i18n.t("workspace.members.role.memberDescription"), }, ] createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { setStore("show", false) } }) function show() { while (true) { submission.clear() if (!submission.result) break } setStore("show", true) setStore("selectedRole", "member") setStore("limit", "") setTimeout(() => input?.focus(), 0) } function hide() { setStore("show", false) } return (

{i18n.t("workspace.members.title")}

{i18n.t("workspace.members.subtitle")}

{i18n.t("workspace.members.beta.beforeLink")}{" "} {i18n.t("common.learnMore")} .

{i18n.t("workspace.members.form.invitee")}

(input = r)} data-component="input" name="email" type="text" placeholder={i18n.t("workspace.members.form.emailPlaceholder")} />

{i18n.t("workspace.members.form.role")}

setStore("selectedRole", value as (typeof UserRole)[number])} />

{i18n.t("workspace.members.form.monthlyLimit")}

setStore("limit", e.currentTarget.value)} min="0" />
{(err) =>
{localizeError(i18n.t, err())}
}
0}> {(member) => ( )}
{i18n.t("workspace.members.table.email")} {i18n.t("workspace.members.table.role")} {i18n.t("workspace.members.table.monthLimit")}
) }